package com.idealighter.game.games.blackjack.manager;

import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import com.google.inject.Singleton;

import com.idealighter.game.common.CommonMsgMgr;
import com.idealighter.game.core.assertions.HuohuaAssert;
import com.idealighter.game.core.common.Game;
import com.idealighter.game.core.constant.Operator;
import com.idealighter.game.core.constant.room.RoomActiveConstant;
import com.idealighter.game.core.error.ErrorCode;
import com.idealighter.game.dblog.service.DbLogService;
import com.idealighter.game.dictionary.dic.BlackjackTimeDic;
import com.idealighter.game.dictionary.domain.BlackjackRoomDomain;
import com.idealighter.game.event.manager.EventMgr;
import com.idealighter.game.event.struct.CancelPlayerExitEvent;
import com.idealighter.game.event.struct.ClearGameDataEvent;
import com.idealighter.game.event.struct.GameClearExitEvent;
import com.idealighter.game.event.struct.PlayerExitEvent;
import com.idealighter.game.event.struct.ShutdownGameEvent;
import com.idealighter.game.event.struct.ShutdownRoomEvent;
import com.idealighter.game.event.struct.StartGameEvent;
import com.idealighter.game.event.struct.StartRoomEvent;
import com.idealighter.game.gamehall.dto.MemInfo;
import com.idealighter.game.gamehall.event.EnterRoomEvent;
import com.idealighter.game.gamehall.event.EnterTableEvent;
import com.idealighter.game.gamehall.event.ExitRoomEvent;
import com.idealighter.game.gamehall.event.ExitTableEvent;
import com.idealighter.game.games.blackjack.message.ResGameInfoMsg;
import com.idealighter.game.games.blackjack.struct.BlackjackCardSeat;
import com.idealighter.game.games.blackjack.struct.BlackjackCardsType;
import com.idealighter.game.games.blackjack.struct.BlackjackCardsTypeComparator;
import com.idealighter.game.games.blackjack.struct.BlackjackCardsTypeGetter;
import com.idealighter.game.games.blackjack.struct.BlackjackRoom;
import com.idealighter.game.games.blackjack.struct.BlackjackSeat;
import com.idealighter.game.games.blackjack.struct.BlackjackTable;
import com.idealighter.game.games.blackjack.struct.GameStatus;
import com.idealighter.game.games.common.AbstractRoom;
import com.idealighter.game.games.common.InstanceState;
import com.idealighter.game.games.common.SeatState;
import com.idealighter.game.log.core.LogReason;
import com.idealighter.game.log.struct.game.Game24WinLoseLog;
import com.idealighter.game.log.struct.game.GameAfeeLog;
import com.idealighter.game.login.manager.LoginMgr;
import com.idealighter.game.message.core.ResMessage;
import com.idealighter.game.player.manager.PlayerMgr;
import com.idealighter.game.player.service.bo.PlayerBo;
import com.idealighter.game.player.struct.Player;
import com.idealighter.game.schedule.manager.ScheduleMgr;
import com.idealighter.game.server.ExecutorMgr;
import com.idealighter.game.server.core.executor.DisruptorExecutor;
import com.idealighter.utils.check.EmptyUtil;
import com.idealighter.utils.json.JsonUtil;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Singleton
public class BlackjackMgr {
  private static final Logger LOG = LoggerFactory.getLogger(BlackjackMgr.class);

  // 定时服务器增加的延时，因为与网络延时等原因
  private static final int ADDED_DELAY = 3;

  // 不下注踢出
  private static final int NOT_BET_OUT_TIMES = 3;

  @Inject
  private CommonMsgMgr commonMsgMgr;
  // @Inject
  // private ChatMsgMgr chatMsgMgr;
  @Inject
  private ScheduleMgr scheduleMgr;
  @Inject
  private PlayerMgr playerMgr;
  @Inject
  private LoginMgr loginMgr;

  @Inject
  private BlackjackDataMgr dataMgr;

  @Inject
  private BlackjackMsgMgr msgMgr;

  @Inject
  private BlackjackTimeDic timeDic;

  // 事件管理器
  private final EventMgr eventMgr;
  // 游戏线程executor,可以执行普通任务和定时任务
  private final DisruptorExecutor gameExecutor;

  private Vector<Long> logoutPlayers = new Vector<>();

  @Inject
  private BlackjackControlScript controlScript;

  /**
   * 构造函数.
   * 
   * @param eventMgr 事件管理.
   * @param executorMgr 游戏线程.
   */
  @Inject
  public BlackjackMgr(EventMgr eventMgr, ExecutorMgr executorMgr) {
    this.eventMgr = eventMgr;
    eventMgr.register(this);
    this.gameExecutor = executorMgr.getGameExecutor(Game.BLACK_JACK.getModuleId());
  }

  private void clearLogoutPlayer() {
    if (logoutPlayers != null && !logoutPlayers.isEmpty()) {
      logoutPlayers.forEach(playerId -> {
        BlackjackSeat seat = dataMgr.getPlayerSeat(playerId);
        Player player = playerMgr.getPlayer(playerId);
        if (seat != null) {
          // 玩家在房间中退出桌子
          doExitTable(player, seat);
        }

        // 玩家是否在21点房间中
        if (player != null && player.curRoom instanceof BlackjackRoom) {
          // 退出房间
          doExitRoom(player);

          GameClearExitEvent gameClearExitEvent =
              new GameClearExitEvent(player, Game.BLACK_JACK.getDesc());
          eventMgr.post(gameClearExitEvent);
        }
      });
      logoutPlayers.clear();
    }
  }

  /**
   * 玩家离开监听.
   * 
   * @param event 玩家离开事件.
   */
  @Subscribe
  public void onPlayerExitGame(PlayerExitEvent event) {
    gameExecutor.execute(() -> {
      Player player = event.player;
      if (player.curRoom instanceof BlackjackRoom) {
        long playerId = player.getId();

        BlackjackSeat seat = dataMgr.getPlayerSeat(playerId);
        if (seat != null && seat.getState() != SeatState.GAMING) { // 没在游戏直接退出
          // 玩家在房间中退出桌子
          doExitTable(player, seat);
          // 退出房间
          doExitRoom(player);

          GameClearExitEvent gameClearExitEvent =
              new GameClearExitEvent(player, Game.BLACK_JACK.getDesc());
          eventMgr.post(gameClearExitEvent);
        } else if (!logoutPlayers.contains(playerId)) {
          logoutPlayers.add(playerId);
          LOG.info("[21点]玩家预约退出 {}", playerId);
        }
      }
    });
  }

  /**
   * 取消玩家退出游戏.
   * 
   * @param event 退出游戏事件.
   */
  @Subscribe
  public void onCancelPlayerExitEvent(CancelPlayerExitEvent event) {
    gameExecutor.execute(() -> {
      Player player = event.player;
      long playerId = player.getId();
      if (logoutPlayers.contains(playerId)) {
        logoutPlayers.remove(playerId);
        LOG.info("[21点]玩家取消退出 {}", playerId);
      }
    });
  }

  private void deleteRooms(List<BlackjackRoom> deleteRooms) {
    if (EmptyUtil.listIsNotEmpty(deleteRooms)) {
      for (Iterator<BlackjackRoom> iterator = deleteRooms.iterator(); iterator.hasNext();) {
        BlackjackRoom blackjackRoom = iterator.next();
        List<BlackjackTable> tables = new ArrayList<>(blackjackRoom.tables());
        for (Iterator<BlackjackTable> tableIterator = tables.iterator(); tableIterator.hasNext();) {
          BlackjackTable blackjackTable = tableIterator.next();
          checkClose(blackjackTable);
        }
        dataMgr.checkRemoveInstance(blackjackRoom.getId().intValue(),
            blackjackRoom.getInstanceId());
      }
    }
  }

  /**
   * 房间开启事件监听.
   * 
   * @param event 房间开启事件.
   */
  @Subscribe
  public void onStartRoom(StartRoomEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.BLACK_JACK) {
        List<BlackjackRoom> deleteRooms = dataMgr.startRoom(event.getRoomId(), true);
        deleteRooms(deleteRooms);
      }
    });
  }

  /**
   * 关闭房间事件监听.
   * 
   * @param event 事件.
   */
  @Subscribe
  public void onShutDownRoom(ShutdownRoomEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.BLACK_JACK) {
        List<BlackjackRoom> deleteRooms = dataMgr.deleteRoom(event.getRoomId());
        deleteRooms(deleteRooms);
      }
    });
  }

  /**
   * 开启游戏 .
   *
   * @author abin
   * @date 2018年5月18日 上午11:24:51
   * @param event 开启游戏事件.
   */
  @Subscribe
  public void onStartGame(StartGameEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.BLACK_JACK) {
        List<Integer> ids = dataMgr.reloadRoom();
        if (EmptyUtil.listIsNotEmpty(ids)) {
          for (Integer id : ids) {
            List<BlackjackRoom> deleteRooms = dataMgr.startRoom(id, false);
            deleteRooms(deleteRooms);
          }
        }
      }
    });
  }

  /**
   * 关闭游戏 .
   *
   * @author abin
   * @date 2018年5月18日 上午11:24:51
   * @param event 关闭游戏事件.
   */
  @Subscribe
  public void onShutdownGame(ShutdownGameEvent event) {
    gameExecutor.execute(() -> {
      if (event.getGame() == Game.BLACK_JACK) {
        Collection<BlackjackRoom> rooms = dataMgr.allRooms();
        if (!EmptyUtil.isEmpty(rooms)) {
          for (BlackjackRoom room : rooms) {
            List<BlackjackRoom> deleteRooms = dataMgr.deleteRoom(room.getId().intValue());
            deleteRooms(deleteRooms);
          }
        }

      }
    });
  }

  @Subscribe
  public void onClearGameData(ClearGameDataEvent event) {
    gameExecutor.execute(() -> doClearGameData(event.playerId));
  }



  /**
   * 筹码换算金币 .
   * 
   * @param roomId 房间Id.
   * @param chips 筹码.
   */
  private long convertGold(BlackjackRoomDomain roomDom, long chips) {
    return chips * roomDom.getProportionGold() / roomDom.getProportionChips();
  }

  /**
   * 金币换算筹码 .
   * 
   * @param roomId 房间Id.
   * @param gold 金币.
   * @return 筹码.
   */
  private long convertChip(BlackjackRoomDomain roomDom, long gold) {
    return gold * roomDom.getProportionChips() / roomDom.getProportionGold();
  }

  /**
   * 玩家进入21点大厅 .
   * 
   * @param player 玩家.
   */
  public ResMessage enterGameHall(Player player) {
    // 不应该已经进入游戏中.
    if (player.curRoom != null) {
      Game game = player.curRoom.game();
      LOG.warn("[21点]玩家[{}][{}]在[{}]游戏中，暂时无法进入游戏", player.getId(), player.getPlayerName(),
          game.getDesc());

      HuohuaAssert.isTrue(player.curRoom == null, ErrorCode.GAME_ALREADY_IN,
          player.curRoom.game().getDesc());
    }


    LOG.info("[21点]玩家[{}][{}]进入大厅成功", player.getPlayerName(), player.getId());
    LOG.info(JsonUtil.toJson(msgMgr.resEnterGameHallMsg()));
    return msgMgr.resEnterGameHallMsg();
  }


  /**
   * 玩家进入房间 .
   * 
   * @param player 玩家.
   * @param roomId 房间Id.
   */
  public ResMessage enterRoom(Player player, int roomId) {
    AbstractRoom curRoom = player.curRoom;

    HuohuaAssert.isTrue(curRoom == null);

    // 获取最新的房间
    BlackjackRoom room = dataMgr.getNewestRoom(roomId);
    HuohuaAssert.isTrue(room != null && room.getInstanceState().equals(InstanceState.NORMAL),
        ErrorCode.GAME_ROOM_CLOSED);

    BlackjackRoomDomain roomDom = room.getRoomDomain();
    HuohuaAssert.isTrue(roomDom.getIsActive() == RoomActiveConstant.ACTIVE);

    HuohuaAssert.isTrue(player.getGold() >= roomDom.getLower(), ErrorCode.GAME_BELOW_MIN_GOLD);

    HuohuaAssert.isTrue(player.getGold() <= roomDom.getUpper(), ErrorCode.GAME_BEYOND_MAX_GOLD);



    if (!player.vip()
        && room.getPlayers().size() >= roomDom.getMaxNum() * roomDom.getOrdinarPeople() / 100) {
      commonMsgMgr.sendErrorDlgMsg(player, ErrorCode.GAME_NOT_VIP_ROOM_FULL, Operator.SYSTEM);
      LOG.warn("[21点]玩家[{}][{}]进入的房间[{}]普通玩家人数已超过上限", player.getPlayerName(), player.getId(),
          roomId, roomDom.getMaxNum());
      HuohuaAssert.isTrue(false, ErrorCode.GAME_NOT_VIP_ROOM_FULL);
    }

    HuohuaAssert.isTrue(room.getPlayers().size() < roomDom.getMaxNum(), ErrorCode.GAME_ROOM_FULL);

    // 修改玩家当前房间
    player.curRoom = room;
    room.getPlayers().add(player.getId());


    LOG.info("[21点]玩家[{}][{}]进入的房间[{}]成功", player.getPlayerName(), player.getId(), roomId);
    // 触发进入房间事件
    eventMgr.post(new EnterRoomEvent(player, room));

    // 发送进入房间消息
    return msgMgr.resEnterRoomMsg(player);
  }



  private void doExitRoom(Player player) {
    AbstractRoom curRoom = player.curRoom;
    BlackjackSeat seat = dataMgr.getPlayerSeat(player.getId());

    HuohuaAssert.isTrue(curRoom instanceof BlackjackRoom);
    // 如果还在座位中，要先站起
    HuohuaAssert.isTrue(seat == null, ErrorCode.GAME_EXIT_ROOM_EXIT_SIT_FIRST);


    player.curRoom = null;
    BlackjackRoom room = (BlackjackRoom) curRoom;
    room.getPlayers().remove(player.getId());

    LOG.info("[21点]玩家[{}][{}]退出房间[{}]", player.getPlayerName(), player.getId(), room.getId());
    // 触发退出房间事件
    eventMgr.post(new ExitRoomEvent(player, curRoom));
  }

  /**
   * 玩家退出房间.
   * 
   * @param player 玩家.
   */
  public ResMessage resExitRoom(Player player) {
    doExitRoom(player);
    return ResMessage.DEFAULT;
  }

  /**
   * 被踢出房间 .
   *
   * @author abin
   * @date 2018年3月22日 下午5:22:09
   * @param player 玩家.
   */
  private void kickoutRoom(Player player) {
    doExitRoom(player);
    msgMgr.noticeKickoutRoom(player);
  }

  /**
   * 房间关闭踢出-通知 .
   *
   * @author abin
   * @date 2018年4月28日 下午5:08:21
   * @param player 玩家.
   */
  private void kickoutForRoomClose(Player player) {
    BlackjackSeat seat = dataMgr.getPlayerSeat(player.getId());
    if (seat != null) {
      doExitTable(player, seat);
    }
    if (player.curRoom != null) {
      doExitRoom(player);
      commonMsgMgr.noticeRoomCloseKickout(player);
    }

  }

  private BlackjackSeat findEmptySeatNotCurrentTable(Player player, BlackjackRoom room,
      Integer tableId) {
    BlackjackSeat emptySeat = null;
    ArrayList<BlackjackTable> tables = new ArrayList<>(room.getTables().values());
    Collections.shuffle(tables);

    for (BlackjackTable table : tables) {
      if (table.getPassword() != null) {
        continue;
      }
      if (table.getId().intValue() == tableId) {
        continue;
      }
      // 考虑是否过滤正在游戏中的table实例
      // if (table.status != GameStatus.) {
      // continue;
      // }

      if (table.getLimitIp() != null && table.getLimitIp().equals(player.ip)) {
        continue;
      }

      if (player.getGold() < table.getLimitGold()) {
        continue;
      }

      boolean hasPlayer = false;
      BlackjackSeat tableEmptySeat = null;

      for (BlackjackSeat seat : table.getSeats()) {
        if (seat.getPlayerId() == 0) {
          emptySeat = seat;
          tableEmptySeat = seat;
        } else {
          hasPlayer = true;
        }
      }

      if (hasPlayer && tableEmptySeat != null) {
        return tableEmptySeat;
      }
    }

    return emptySeat;
  }

  /**
   * 查找空位置,优先查找有人的桌子 .
   * 
   * @param player 玩家.
   * @param room .
   * @return
   */
  private BlackjackSeat findEmptySeat(Player player, BlackjackRoom room) {
    BlackjackSeat emptySeat = null;
    ArrayList<BlackjackTable> tables = new ArrayList<>(room.getTables().values());
    Collections.shuffle(tables);

    for (BlackjackTable table : tables) {
      if (table.getPassword() != null) {
        continue;
      }
      // 考虑是否过滤正在游戏中的table实例
      // if (table.status != GameStatus.) {
      // continue;
      // }

      if (table.getLimitIp() != null && table.getLimitIp().equals(player.ip)) {
        continue;
      }

      if (player.getGold() < table.getLimitGold()) {
        continue;
      }

      boolean hasPlayer = false;
      BlackjackSeat tableEmptySeat = null;

      for (BlackjackSeat seat : table.getSeats()) {
        if (seat.getPlayerId() == 0) {
          emptySeat = seat;
          tableEmptySeat = seat;
        } else {
          hasPlayer = true;
        }
      }

      if (hasPlayer && tableEmptySeat != null) {
        return tableEmptySeat;
      }
    }

    return emptySeat;
  }

  /**
   * 玩家进入牌桌.
   * 
   * @param player 玩家.
   * @param tableId 桌子Id.
   */
  public ResMessage enterTable(Player player, int tableId) {
    HuohuaAssert.isTrue(player.curRoom instanceof BlackjackRoom);

    BlackjackTable table = dataMgr.getTable(tableId);
    HuohuaAssert.isTrue(table != null, ErrorCode.GAME_ROOM_CLOSED);

    HuohuaAssert.isTrue(dataMgr.getPlayerSeat(player.getId()) == null);

    BlackjackRoom room = (BlackjackRoom) player.curRoom;
    BlackjackSeat seat = findEmptySeat(player, room);
    HuohuaAssert.isTrue(seat != null && seat.getPlayerId() <= 0, ErrorCode.GAME_SET_NOT_EMPTY);

    // 进入座位
    List<MemInfo> memInfos = doEnterTable(player, seat);
    msgMgr.noticeFastEnterTableMsg(player, memInfos);

    return ResMessage.DEFAULT;
  }

  /**
   * 快速进入21点.
   * 
   * @param player 玩家.
   * @param roomId 房间Id.
   */
  public ResMessage fastEnterTable(Player player, int roomId) {
    HuohuaAssert.isTrue(player.curRoom instanceof BlackjackRoom);

    BlackjackSeat seat = dataMgr.getPlayerSeat(player.getId());
    HuohuaAssert.isTrue(seat == null);


    BlackjackRoom room = dataMgr.getNewestRoom(roomId);

    HuohuaAssert.isTrue(room != null && room.getInstanceState().equals(InstanceState.NORMAL),
        ErrorCode.GAME_ROOM_CLOSED);

    seat = findEmptySeat(player, room);
    HuohuaAssert.isTrue(seat != null && seat.getPlayerId() <= 0, ErrorCode.GAME_NOT_SIT);

    // 进入座位
    List<MemInfo> memInfos = doEnterTable(player, seat);

    // 发送快速进入桌子结果失败
    return msgMgr.resFastEnterTableMsg(memInfos, seat.getTotalChips());
  }



  /**
   * 玩家进入座位,玩家每次进入21点桌子扣台费(金币) .
   * 
   * @param player 玩家.
   * @param seat 座位.
   */
  private List<MemInfo> doEnterTable(Player player, BlackjackSeat seat) {
    BlackjackTable table = seat.getTable();
    BlackjackRoom room = table.getRoom();
    BlackjackRoomDomain roomDom = room.getRoomDomain();
    HuohuaAssert.isTrue(player.getGold() >= roomDom.getLower(), ErrorCode.PLAYER_GOLD_NOT_ENOUGH);

    HuohuaAssert.isTrue(player.getGold() <= roomDom.getUpper(),
        ErrorCode.PLAYER_GOLD_BEYOND_MAX_GOLD);

    // 台费(金币)
    int afee = roomDom.getAfee();

    HuohuaAssert.isTrue(player.getGold() >= afee, ErrorCode.GAME_NOT_ENOUGH_FEE);

    if (afee > 0) {
      playerMgr.minusGold(player, afee, LogReason.BLACKJACK_ENTER_TABLE_MINUS_AFEE);
      DbLogService.log(new GameAfeeLog(player, table, afee));
    }

    seat.setPlayerId(player.getId());
    seat.setPlayerName(player.getPlayerName());
    seat.setSex(player.getSex());
    seat.setRobot(player.robot());
    seat.setState(SeatState.SEATED);

    // 初始化筹码为金币数
    seat.setTotalChips(convertChip(roomDom, player.getGold())); // player.getGold();

    // 更新玩家座位
    dataMgr.updatePlayerSeats(player.getId(), seat);
    // 触发进入桌子事件
    eventMgr.post(new EnterTableEvent(player, seat));

    LOG.info("[21点]玩家[{}][{}]进入桌子[{}]成功", player.getPlayerName(), player.getId(), table.getId());

    player.curSeat = seat;

    // 桌子玩家列表
    List<MemInfo> mems = getTableMemInfos(table);

    msgMgr.noticeEnterTableMsg(seat);

    // 未开始游戏，则开始游戏.
    if (table.stepFuture == null && !table.isGameing()) {
      gameStart(table);
    }

    return mems;
  }

  /**
   * 获取房间成员信息 .
   *
   * @author abin
   * @date 2018年4月8日 下午4:29:07
   * @param table 桌子信息.
   * @return 成员信息.
   */
  public List<MemInfo> getTableMemInfos(BlackjackTable table) {
    // 桌子玩家列表
    AbstractRoom room = table.room();
    List<MemInfo> mems = new ArrayList<>();
    for (Long playerId : table.players()) {
      mems.add(room.memInfo(playerId));
    }

    return mems;
  }



  /**
   * 玩家退出牌桌.
   * 
   * @param player 玩家.
   */
  public ResMessage resExitTable(Player player) {
    BlackjackSeat seat = dataMgr.getPlayerSeat(player.getId());

    HuohuaAssert.isTrue(seat != null);

    doExitTable(player, seat);

    // 发送退出房间消息
    return ResMessage.DEFAULT;
  }

  /**
   * 退出桌子.
   * 
   * @param seat 座位.
   */
  private void doExitTable(Player player, BlackjackSeat seat) {

    HuohuaAssert.isTrue(seat.getState() != SeatState.GAMING, ErrorCode.GAME_PLAYING_NOT_LEAVE);

    msgMgr.noticeExitTableMsg(seat);

    dataMgr.removePlayerSeat(seat.getPlayerId());

    player.curSeat = null;

    player.curTable = null;

    LOG.info("[21点]玩家[{}][{}]退出桌子[{}]", seat.getPlayerId(), seat.getPlayerName(),
        seat.getTable().getId());
    seat.clear();
    // 触发退出桌子事件
    eventMgr.post(new ExitTableEvent(player, seat));
  }

  /**
   * 被提出桌子 .
   *
   * @author abin
   * @date 2018年3月22日 下午5:21:40
   * @param player 玩家.
   * @param seat 座位信息.
   */
  private void kickoutTable(Player player, BlackjackSeat seat) {
    doExitTable(player, seat);
    msgMgr.noticeKickoutTable(player);
  }


  /**
   * 快速换桌 .
   *
   * @author abin
   * @date 2018年3月22日 下午5:46:09
   * @param player 玩家.
   * @return 结果.
   */
  public ResMessage resExchangeTable(Player player) {
    BlackjackSeat seat = dataMgr.getPlayerSeat(player.getId());
    HuohuaAssert.isTrue(seat != null);

    HuohuaAssert.isTrue(seat.getState() == SeatState.SEATED);

    BlackjackRoom room = seat.getTable().getRoom();

    HuohuaAssert.isTrue(player.getGold() >= room.getRoomDomain().getLower(),
        ErrorCode.PLAYER_GOLD_NOT_ENOUGH);

    HuohuaAssert.isTrue(player.getGold() <= room.getRoomDomain().getUpper(),
        ErrorCode.PLAYER_GOLD_BEYOND_MAX_GOLD);

    BlackjackSeat exchangedSeat =
        findEmptySeatNotCurrentTable(player, room, seat.getTable().getId().intValue());

    HuohuaAssert.isTrue(exchangedSeat != null);

    doExitTable(player, seat);

    // 进入座位
    List<MemInfo> memInfos = doEnterTable(player, exchangedSeat);

    // 发送快速进入桌子结果失败
    return msgMgr.resExchangeTableMsg(memInfos, seat.getTotalChips());
  }


  /**
   * 获取当前游戏信息 .
   *
   * @author abin
   * @date 2018年4月8日 上午11:17:26
   * @param player 玩家.
   * @return 游戏信息.
   */
  public ResMessage getGameInfo(Player player) {
    BlackjackSeat seat = dataMgr.getPlayerSeat(player.getId());
    // 校验参数
    ResMessage msg = null;
    if (player.curRoom instanceof BlackjackRoom && player.curSeat != null && seat != null) {

      int time = timeDic.map().get(seat.getTable().getStatus().getVal()).getTime();

      if (seat.getTable().stepFuture != null) {
        int tempTime = (int) seat.getTable().stepFuture.getDelay(TimeUnit.SECONDS);
        if (tempTime < time) {
          time = tempTime;
        }
      }

      // 进入座位
      List<MemInfo> memInfos = getTableMemInfos(seat.getTable());

      seat.setTotalChips(convertChip(seat.getTable().getRoom().getRoomDomain(), player.getGold()));

      msg = msgMgr.resGameInfo(player, time, memInfos);
    } else {
      msg = new ResGameInfoMsg();
    }

    return msg;
  }


  /**
   * 检验是否房间关闭，如果桌子不在游戏中或者游戏正在休息，顺便把桌子关闭 .
   *
   * @author abin
   * @date 2018年4月28日 下午3:41:20
   * @param table 桌子.
   */
  private boolean checkClose(BlackjackTable table) {
    boolean remove = false;
    if (table.getRoom().getInstanceState() == InstanceState.TO_REMOVE) {
      if (!table.isGameing() || table.getStatus().equals(GameStatus.REST)) {
        for (Long playerId : table.players()) {
          Player player = playerMgr.getPlayer(playerId);
          if (player != null) {
            kickoutForRoomClose(player);
          }
        }
        BlackjackRoom room = table.getRoom();
        room.removeTable(table.getId().intValue());
        if (table != null && table.stepFuture != null) {
          table.stepFuture.cancel(false);
        }

        table = null;
        remove = true;
      }
    }
    return remove;
  }

  /**
   * 开始游戏 .
   *
   * @author abin
   * @date 2018年3月23日 上午10:26:28
   * @param table 桌子.
   */
  private void gameStart(BlackjackTable table) {
    // 重置游戏数据
    // 1.可能洗牌
    // 2.重置庄家和玩家游戏信息

    table.resetGameData();

    if (table.hasPlayer()) {
      table.setGameing(true);
      LOG.info("[21点]{} 开始游戏", table.getId());
      // 有玩家开始游戏
      doRest(table);
    } else {
      // 清空预约退出玩家.
      clearLogoutPlayer();

      table.stepFuture = null;
      table.setGameing(false);

      if (checkClose(table)) {
        dataMgr.checkRemoveInstance(table.getRoom().getId().intValue(),
            table.getRoom().getInstanceId());
        return;
      }

      LOG.info("[21点]{} 游戏暂停", table.getId());

    }
  }

  /**
   * 休息 .
   *
   * @author abin
   * @date 2018年3月23日 上午10:26:28
   * @param table 桌子.
   */
  private void doRest(BlackjackTable table) {
    table.setStatus(GameStatus.REST);

    if (checkClose(table)) {
      dataMgr.checkRemoveInstance(table.getRoom().getId().intValue(),
          table.getRoom().getInstanceId());
      return;
    }

    int time = timeDic.map().get(table.getStatus().getVal()).getTime();

    // 通知客户端休息时间
    msgMgr.noticeGameStage(table, time);

    LOG.info("[21点]{} 休息()", table.getId());

    table.stepFuture = scheduleMgr.schedule(() -> {
      doBet(table);
    }, time + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
  }

  /**
   * 下注 .
   *
   * @author abin
   * @date 2018年3月23日 上午10:26:39
   * @param table 桌子.
   */
  private void doBet(BlackjackTable table) {
    table.setStatus(GameStatus.BET);
    int time = timeDic.map().get(table.getStatus().getVal()).getTime();

    // 通知下注时间
    msgMgr.noticeGameStage(table, time);

    LOG.info("[21点] 下注{}", table.getId());


    table.stepFuture = scheduleMgr.schedule(() -> {
      doDealCard(table);
    }, time + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
  }

  /**
   * 快速下注结束 .
   *
   * @author abin
   * @date 2018年3月23日 下午5:21:04
   * @param table 桌子.
   */
  private void quickBetOver(BlackjackTable table) {
    if (table.isAllBetOver()) {
      LOG.info("全部都下注完了");
      // 因为同一个线程，所以不存在取消不掉的情况，因为下注的时候已经限定只能在下注阶段
      // 加入判断 只是为了让代码更好理解
      if (table.stepFuture.cancel(false)) {
        doDealCard(table);
      }
    }
  }

  private void clearPlayerForNotBet(BlackjackTable table) {
    for (BlackjackSeat seat : table.getSeats()) {
      if (seat.getPlayerId() > 0) {
        if (!seat.isHasBet()) {
          seat.setNobetTimes(seat.getNobetTimes() + 1);
        } else {
          seat.setNobetTimes(0);
        }
        if (seat.getNobetTimes() >= NOT_BET_OUT_TIMES) {
          Player player = playerMgr.getPlayer(seat.getPlayerId());
          if (player != null) {
            kickoutTable(player, seat);
            kickoutRoom(player);
          }
        }
        // long gold = convertGold(table.room.id, seat.totalChips);
        // if (gold < roomDom.getLower() || gold > roomDom.getUpper()) {
        // Player player = playerMgr.getPlayer(seat.playerId);
        // if (player != null) {
        // kickoutTable(player, seat);
        // kickoutRoom(player);
        // }
        // }
      }
    }
  }

  /**
   * 发牌 .
   *
   * @author abin
   * @date 2018年3月23日 上午10:26:55
   * @param table 桌子.
   */
  private void doDealCard(BlackjackTable table) {
    if (!table.hasPlayerBet()) { // 没人下注重新开始.
      // 游戏重新开始
      gameStart(table);

      // 清空不玩玩家
      clearPlayerForNotBet(table);
    } else {
      table.setStatus(GameStatus.DEAL);
      int time = timeDic.map().get(table.getStatus().getVal()).getTime();
      // 发牌
      table.dealCard();

      // 发牌通知
      msgMgr.noticeDealCardStage(table);

      LOG.info("[21点] 发牌{}", table.getId());
      // TODO 考虑保险流程是否定时

      // 判定第一张是否A
      if (table.isFirstA()) { // 是，走保险流程
        table.stepFuture = scheduleMgr.schedule(() -> {
          doInsurance(table);
        }, time + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
      } else if (table.getBankerTypeAndPoint().getCardsType() == BlackjackCardsType.BLACK_JACK) {
        // 1.通知庄家牌型
        // 2.结算
        table.stepFuture = scheduleMgr.schedule(() -> {
          doBankerOp(table);
        }, ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);

      } else { // 否,则开始操作
        table.stepFuture = scheduleMgr.schedule(() -> {
          doPlayerOp(table);
        }, time + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
      }
    }
  }

  private void doInsurance(BlackjackTable table) {
    table.setStatus(GameStatus.INSURANCE);
    int time = timeDic.map().get(table.getStatus().getVal()).getTime();

    // 通知下注保险时间
    msgMgr.noticeGameStage(table, time);

    LOG.info("[21点] 保险{}", table.getId());

    table.stepFuture = scheduleMgr.schedule(() -> {
      doInsuranceBill(table);
    }, time + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
  }


  /**
   * 快速保险结束 .
   *
   * @author abin
   * @date 2018年3月23日 下午5:21:04
   * @param table 桌子.
   */
  private void quickInsuranceOver(BlackjackTable table) {
    if (table.isAllInsuranceOver()) {
      // 因为同一个线程，所以不存在取消不掉的情况
      // 加入判断 只是为了让代码更好理解
      if (table.stepFuture.cancel(false)) {
        table.stepFuture = scheduleMgr.schedule(() -> {
          doInsuranceBill(table);
        }, ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
      }
    }
  }

  private void doInsuranceBill(BlackjackTable table) {
    table.setStatus(GameStatus.INSURANCE_BILL);


    boolean isBlackjack =
        table.getBankerTypeAndPoint().getCardsType().equals(BlackjackCardsType.BLACK_JACK);
    boolean hasJoin = false;
    for (BlackjackSeat seat : table.getSeats()) {
      if (seat.isHasInsurance()) {
        seat.setChangeBet(0);
        hasJoin = true;
        long playerId = seat.getPlayerId();

        PlayerBo playerDom = playerMgr.selectPlayer(playerId);
        if (isBlackjack) {
          seat.setBillChips(seat.getInsuranceBet() * 3);

          Player player = playerMgr.getPlayer(playerId);
          long billGold = convertGold(table.getRoom().getRoomDomain(), seat.getBillChips());
          if (player != null) {
            playerMgr.addGold(player, billGold, true, false, LogReason.BLACKJACK_INSURANCE_BILL);
          } else {
            playerMgr.addGold(playerDom, billGold, LogReason.BLACKJACK_INSURANCE_BILL);
          }

          seat.setTotalChips(seat.getTotalChips() + seat.getBillChips());
        }


        // 最终结果输赢筹码
        long resChips = seat.getBillChips() - seat.getInsuranceBet();
        seat.setChangeBet(resChips);
        long resGold = convertGold(table.getRoom().getRoomDomain(), resChips);

        // 记录log
        DbLogService.log(new Game24WinLoseLog(playerDom, seat, resChips, resGold,
            LogReason.BLACKJACK_INSURANCE_BILL));
        // 结算完 清0
        seat.setBillChips(0);
        seat.getTable().setSysWin(seat.getTable().getSysWin() - resChips);
      }
    }
    if (hasJoin) {
      // 通知保险结算
      msgMgr.noticeStageInsuranceBill(table, isBlackjack);

      LOG.info("[21点] 保险结算{}", table.getId());
    }
    for (BlackjackSeat seat : table.getSeats()) {
      if (seat.getPlayerId() > 0 && seat.isHasInsurance()) {
        seat.setChangeBet(0);
      }
    }

    int time = timeDic.map().get(table.getStatus().getVal()).getTime();
    if (!isBlackjack) {
      table.stepFuture = scheduleMgr.schedule(() -> {
        doPlayerOp(table);
      }, time + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
    } else {
      table.stepFuture = scheduleMgr.schedule(() -> {
        doBankerOp(table);
      }, time + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
    }

  }

  /**
   * 玩家操作 .
   *
   * @author abin
   * @date 2018年3月23日 上午10:27:09
   * @param table 桌子.
   */
  private void doPlayerOp(BlackjackTable table) {
    // 如果没有全部轮转一遍，则新一回合
    table.setStatus(GameStatus.PLAYER_OP);
    int time = timeDic.map().get(table.getStatus().getVal()).getTime();

    BlackjackSeat currentSeat = null;
    BlackjackCardSeat currentCardSeat = null;
    LOG.info("[21点] 玩家操作 开始");
    while (table.getActiveSeatOrder() < table.getSeats().size()) {
      // 清空，开始查找
      currentSeat = null;
      // 没有手牌
      // 没有下注
      // 跳过
      currentSeat = table.getSeats().get(table.getActiveSeatOrder());
      if (EmptyUtil.listIsEmpty(currentSeat.getCardSeats()) || !currentSeat.isHasBet()) {
        table.setActiveSeatOrder(table.getActiveSeatOrder() + 1);
        table.setActiveCardOrder(0);

        currentSeat = null;

        continue;
      }
      // 清空，开始查找
      currentCardSeat = null;
      while (table.getActiveCardOrder() < currentSeat.getCardSeatsNum()) {
        // 已经操作结束
        // 已经结算
        // 跳过
        currentCardSeat = currentSeat.getCardSeats().get(table.getActiveCardOrder());
        if (currentCardSeat.isOpOver() || currentCardSeat.isBill()) {
          table.setActiveCardOrder(table.getActiveCardOrder() + 1);
          currentCardSeat = null;
          continue;
        }

        // 手牌为黑杰克、五龙、爆牌(即为非普通牌)
        // 跳过
        if (!currentCardSeat.getCardTypeAndPoint().getCardsType()
            .equals(BlackjackCardsType.NORMAL)) {
          table.setActiveCardOrder(table.getActiveCardOrder() + 1);
          currentCardSeat = null;
          continue;
        }
        if (currentSeat != null && currentCardSeat != null) {
          break;
        }
      }
      if (currentSeat != null && currentCardSeat != null) {
        break;
      }
      table.setActiveSeatOrder(table.getActiveSeatOrder() + 1);
      table.setActiveCardOrder(0);

      currentSeat = null;
      currentCardSeat = null;

    }

    final BlackjackSeat tempSeat = currentSeat;
    final BlackjackCardSeat tempCardSeat = currentCardSeat;

    if (currentSeat != null && currentCardSeat != null) {
      msgMgr.noticeStagePlayerOp(currentSeat, currentCardSeat, time);

      LOG.info("[21点] 玩家操作{} 用户{} 座位{} 虚拟牌组座位{}", table.getId(), currentSeat.getPlayerId(),
          currentSeat.getOrder(), currentCardSeat.getCardSeatOrder());

      table.stepFuture = scheduleMgr.schedule(() -> {
        // 玩家操作超时处理
        playerOpTimeOut(tempSeat, tempCardSeat);
      }, time + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
    } else {
      // 如果都操作完，则进入庄家操作.
      table.stepFuture = scheduleMgr.schedule(() -> {
        doBankerOp(table);
      }, ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);

    }
  }

  /**
   * 玩家超时处理，停牌 .
   *
   * @author abin
   * @date 2018年3月27日 上午10:34:37
   * @param currentSeat 当前座位.
   * @param currentCardSeat 当前牌桌.
   */
  private void playerOpTimeOut(BlackjackSeat currentSeat, BlackjackCardSeat currentCardSeat) {
    currentCardSeat.setOpOver(true);

    // 通知补牌结束
    msgMgr.noticePlayerStand(currentSeat);

    LOG.info("[21点]{} 操作超时   玩家{} 座位{} 虚拟牌组座位{} 爆牌结算", currentSeat.getTable().getId(),
        currentSeat.getPlayerId(), currentSeat.getOrder(), currentCardSeat.getCardSeatOrder());

    doPlayerOp(currentSeat.getTable());
  }



  /**
   * 庄家操作 .
   *
   * @author abin
   * @date 2018年3月23日 上午10:27:25
   * @param table 桌子.
   */
  private void doBankerOp(BlackjackTable table) {
    table.setStatus(GameStatus.BANKER_OP);
    int time = timeDic.map().get(table.getStatus().getVal()).getTime();

    LOG.info("[21点] 庄家操作", table.getId());

    // 全部都已经爆牌了，进入快速结算.
    if (table.isAllBilled()
        || table.getBankerTypeAndPoint().getCardsType().equals(BlackjackCardsType.BLACK_JACK)) {
      msgMgr.noticeStageBankerOp(table);
      table.stepFuture = scheduleMgr.schedule(() -> {
        doBill(table);
      }, time + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
    } else { // 需要比牌
      // 庄家补牌.
      controlScript.controlBankerLastCard(table);


      msgMgr.noticeStageBankerOp(table);
      table.stepFuture = scheduleMgr.schedule(() -> {
        doBill(table);
      }, time + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
    }
  }

  private void playerBustBill(Player player, BlackjackSeat seat, BlackjackCardSeat cardSeat) {
    seat.setChangeBet(0);
    // 最终结果输赢筹码
    long resChips = seat.getBillChips() - cardSeat.getBet();
    long resGold = convertGold(seat.getTable().getRoom().getRoomDomain(), resChips);

    // 记录log
    DbLogService
        .log(new Game24WinLoseLog(player, seat, resChips, resGold, LogReason.BLACKJACK_BILL));

    cardSeat.setBill(true);
    // 先不要置0
    // cardSeat.setBet(0L);

    // 结算完 清0
    seat.setBillChips(0);
    seat.setChangeBet(resChips);

    seat.getTable().setSysWin(seat.getTable().getSysWin() - resChips);
    seat.setState(SeatState.SEATED);


    LOG.info("[21点]{} 玩家{} 座位{} 虚拟牌组座位{} 爆牌结算", seat.getTable().getId(), seat.getPlayerId(),
        seat.getOrder(), cardSeat.getCardSeatOrder());

    LOG.info(JsonUtil.toJson(cardSeat.getCardTypeAndPoint()));
  }

  private void playerBill(BlackjackSeat seat) {
    BlackjackRoomDomain roomdom = seat.getTable().getRoom().getRoomDomain();
    boolean billed = true;
    long totalBet = 0;
    seat.setChangeBet(0);
    for (BlackjackCardSeat cardSeat : seat.getCardSeats()) {
      if (!cardSeat.isBill()) { // 未结算
        billed = false;

        int result = BlackjackCardsTypeComparator.compare(seat.getTable().getBankerTypeAndPoint(),
            cardSeat.getCardTypeAndPoint());
        if (result == -1) { // 庄家输
          int multiple =
              BlackjackCardsTypeGetter.multipleCheck(cardSeat.getCardTypeAndPoint().getCardsType());
          seat.setBillChips(seat.getBillChips()
              + cardSeat.getBet() * multiple / BlackjackCardsTypeGetter.MULTIPLE_RATE);

        } else if (result == 0) { // 平
          seat.setBillChips(seat.getBillChips() + cardSeat.getBet());
        } else { // 庄家赢
          seat.setBillChips(seat.getBillChips() + 0);
        }
        // cardSeat.setBill(true);
        totalBet += cardSeat.getBet();
      }
    }
    if (!billed) {
      seat.setTotalChips(seat.getTotalChips() + seat.getBillChips());
      long billGold = convertGold(roomdom, seat.getBillChips());

      // 先加筹码
      long playerId = seat.getPlayerId();
      Player player = playerMgr.getPlayer(playerId);
      PlayerBo playerDom = playerMgr.selectPlayer(playerId);
      if (player != null) {
        playerMgr.addGold(player, billGold, true, false, LogReason.BLACKJACK_BILL);
      } else {
        playerMgr.addGold(playerDom, billGold, LogReason.BLACKJACK_BILL);
      }

      // 最终结果输赢筹码
      long resChips = seat.getBillChips() - totalBet;
      long resGold = convertGold(roomdom, resChips);
      seat.setChangeBet(resChips);

      // 记录log
      DbLogService
          .log(new Game24WinLoseLog(playerDom, seat, resChips, resGold, LogReason.BLACKJACK_BILL));

      seat.getTable().setSysWin(seat.getTable().getSysWin() - resChips);
    }
    seat.setState(SeatState.SEATED);
  }

  /**
   * 结算.
   *
   * @author abin
   * @date 2018年3月23日 上午10:27:43
   * @param table 桌子.
   */
  private void doBill(BlackjackTable table) {
    table.setStatus(GameStatus.BILL);



    // 玩家结算
    for (BlackjackSeat seat : table.getSeats()) {
      if (seat.isHasBet()) {
        playerBill(seat);
      }
    }

    controlScript.balanceControl(table);

    msgMgr.noticeStageBill(table);

    LOG.info("[21点]{} 结算", table.getId());

    // 清空预约退出玩家.
    clearLogoutPlayer();

    int time = timeDic.map().get(table.getStatus().getVal()).getTime();
    // 结算完，重新开始游戏
    table.stepFuture = scheduleMgr.schedule(() -> {
      gameStart(table);
    }, time + ADDED_DELAY, TimeUnit.SECONDS, gameExecutor);
  }

  /**
   * 下注 .
   *
   * @author abin
   * @date 2018年3月23日 上午11:45:55
   * @param player 玩家.
   * @param bet 筹码.
   * @return 结果.
   */
  public ResMessage bet(Player player, long bet) {
    BlackjackSeat seat = dataMgr.getPlayerSeat(player.getId());
    // 校验参数
    HuohuaAssert.isTrue(seat != null && !seat.isBetOver() && bet > 0);
    // 校验阶段
    HuohuaAssert.isTrue(seat.getTable().getStatus() == GameStatus.BET);

    // 校验是否筹码超过上线
    HuohuaAssert.isTrue(seat.getDefaultBet() + bet <= seat.getTable().getRoom().getMaxBet(),
        ErrorCode.GAME_LIMIT_BET);

    // 校验是否有足够筹码
    HuohuaAssert.isTrue(bet <= seat.getTotalChips(), ErrorCode.GAME_CHIP_NOT_ENOUGH);

    BlackjackCardSeat cardSeat = seat.getDefaultCardSeat();

    // 首次下注添加虚拟手牌
    if (cardSeat == null) {
      cardSeat = new BlackjackCardSeat(seat.getCardSeatsNum());
      seat.getCardSeats().add(cardSeat);
    }
    // 更新下注信息
    cardSeat.setBet(cardSeat.getBet() + bet);

    // 下注下注成功
    seat.setTotalChips(seat.getTotalChips() - bet);
    seat.setHasBet(true);
    seat.setState(SeatState.GAMING);

    msgMgr.noticePlayerBet(seat, cardSeat.getBet());

    playerMgr.addGold(seat.getPlayerId(),
        convertGold(seat.getTable().getRoom().getRoomDomain(), -bet), LogReason.BLACKJACK_BET);

    // 筹码已达到最大值自动下注结束
    if (cardSeat.getBet() == seat.getTable().getRoom().getMaxBet()) {
      LOG.info("用户{} 达到最大下注", player.getId());
      seat.setBetOver(true);
      msgMgr.noticePlayerBetOver(seat);

      quickBetOver(seat.getTable());
    }

    return ResMessage.DEFAULT;
  }

  /**
   * 下注结束 .
   *
   * @author abin
   * @date 2018年3月23日 上午11:46:07
   * @param player 玩家.
   * @return 结果.
   */
  public ResMessage betOver(Player player) {
    BlackjackSeat seat = dataMgr.getPlayerSeat(player.getId());
    HuohuaAssert.isTrue(seat != null && seat.isHasBet() && !seat.isBetOver());

    seat.setBetOver(true);

    msgMgr.noticePlayerBetOver(seat);

    quickBetOver(seat.getTable());


    return ResMessage.DEFAULT;
  }


  /**
   * 购买保险 .
   *
   * @author abin
   * @date 2018年3月24日 下午3:43:40
   * @param player 玩家信息.
   * @return 结果.
   */
  public ResMessage insurance(Player player) {
    BlackjackSeat seat = dataMgr.getPlayerSeat(player.getId());

    // 位置校验
    HuohuaAssert.isTrue(seat != null && seat.isHasBet());
    // 桌子校验
    HuohuaAssert
        .isTrue(seat.getTable().getStatus() == GameStatus.INSURANCE && seat.getTable().isFirstA());

    BlackjackCardSeat cardSeat = seat.getDefaultCardSeat();
    long chips = cardSeat.getBet() / 2;
    // 校验是否有足够筹码
    HuohuaAssert.isTrue(chips <= seat.getTotalChips(), ErrorCode.GAME_CHIP_NOT_ENOUGH);

    // 下保险成功
    seat.setHasInsurance(true);
    seat.setInsuranceOver(true);

    seat.setInsuranceBet(chips);
    seat.setTotalChips(seat.getTotalChips() - chips);

    playerMgr.addGold(seat.getPlayerId(),
        convertGold(seat.getTable().getRoom().getRoomDomain(), -chips),
        LogReason.BLACKJACK_INSURANCE);
    // 通知用户下保险
    msgMgr.noticePlayerInsurance(seat);
    quickInsuranceOver(seat.getTable());

    return ResMessage.DEFAULT;
  }

  /**
   * 不买保险..
   *
   * @author abin
   * @date 2018年3月29日 下午8:02:14
   * @param player 玩家.
   * @return 结果.
   */
  public ResMessage unInsurance(Player player) {
    BlackjackSeat seat = dataMgr.getPlayerSeat(player.getId());
    // 位置校验
    HuohuaAssert.isTrue(seat != null && seat.isHasBet());
    // 桌子校验
    HuohuaAssert
        .isTrue(seat.getTable().getStatus() == GameStatus.INSURANCE && seat.getTable().isFirstA());

    // 下保险成功
    seat.setHasInsurance(false);
    seat.setInsuranceOver(true);

    quickInsuranceOver(seat.getTable());

    return ResMessage.DEFAULT;
  }

  /**
   * 操作校验 .
   *
   * @author abin
   * @date 2018年3月27日 下午2:53:09
   * @param cardSeat 牌组座位.
   * @return 结果.
   */
  private boolean opCheck(BlackjackCardSeat cardSeat) {
    boolean isBust = false;
    BlackjackCardsType cardsType = cardSeat.getCardTypeAndPoint().getCardsType();
    switch (cardsType) {
      case BLACK_JACK:
      case FIVE_DRAGON:
        cardSeat.setOpOver(true);
        break;
      case BUST:
        cardSeat.setOpOver(true);
        isBust = true;
        break;
      default:
        break;
    }
    return isBust;
  }



  /**
   * 玩家双倍 .
   *
   * @author abin
   * @date 2018年3月27日 下午3:36:59
   * @param player 玩家.
   * @return 返回消息.
   */
  public ResMessage doubleDown(Player player) {
    BlackjackSeat seat = dataMgr.getPlayerSeat(player.getId());

    HuohuaAssert.isTrue(seat.getOrder() == seat.getTable().getActiveSeatOrder() && seat.isHasBet());

    BlackjackCardSeat cardSeat = seat.getCardSeats().get(seat.getTable().getActiveCardOrder());
    HuohuaAssert.isTrue(cardSeat != null && !cardSeat.isOpOver() && !cardSeat.isBill());


    HuohuaAssert.isTrue(BlackjackCardsTypeGetter.canDoubleDown(cardSeat.getPlayerCards(),
        cardSeat.getCardTypeAndPoint().getCardsType()));


    long bet = cardSeat.getBet();
    HuohuaAssert.isTrue(bet <= seat.getTotalChips(), ErrorCode.GAME_CHIP_NOT_ENOUGH);

    // 双倍成功
    if (seat.getTable().stepFuture.cancel(false)) {

      // 补充牌
      seat.getTable().dealSingleCard(seat, cardSeat);

      // 结算

      seat.setTotalChips(seat.getTotalChips() - bet);
      cardSeat.setBet(cardSeat.getBet() + bet);
      cardSeat.setOpOver(true);
      playerMgr.addGold(seat.getPlayerId(),
          convertGold(seat.getTable().getRoom().getRoomDomain(), -bet),
          LogReason.BLACKJACK_BET_DOUBLE_DOWN);

      // 校验．
      boolean isBust = opCheck(cardSeat);

      // 爆牌结算
      if (isBust) {
        playerBustBill(player, seat, cardSeat);
      }

      // 通知补牌结束
      msgMgr.noticePlayerDoubleDown(seat, cardSeat);

      seat.setChangeBet(0);

      // 下个回合
      doPlayerOp(seat.getTable());
    }

    return ResMessage.DEFAULT;
  }

  /**
   * 玩家分牌 .
   *
   * @author abin
   * @date 2018年3月27日 下午4:49:25
   * @param player 玩家.
   * @return 结果.
   */
  public ResMessage split(Player player) {
    BlackjackSeat seat = dataMgr.getPlayerSeat(player.getId());
    HuohuaAssert.isTrue(seat.getOrder() == seat.getTable().getActiveSeatOrder() && seat.isHasBet());

    BlackjackCardSeat cardSeat = seat.getCardSeats().get(seat.getTable().getActiveCardOrder());
    HuohuaAssert.isTrue(cardSeat != null && !cardSeat.isOpOver() && !cardSeat.isBill());

    HuohuaAssert.isTrue(BlackjackCardsTypeGetter.canSplit(cardSeat.getPlayerCards(),
        cardSeat.getCardTypeAndPoint().getCardsType()));

    long bet = cardSeat.getBet();
    // 校验是否有足够筹码
    HuohuaAssert.isTrue(bet <= seat.getTotalChips(), ErrorCode.GAME_CHIP_NOT_ENOUGH);

    // 分牌成功
    if (seat.getTable().stepFuture.cancel(false)) {

      // 新增一副牌
      BlackjackCardSeat newCardSeat = new BlackjackCardSeat(seat.getCardSeatsNum());
      newCardSeat.setBet(bet);
      seat.getCardSeats().add(newCardSeat);

      // 分牌
      seat.getTable().dealSplitCard(cardSeat, newCardSeat);

      // 添加金币
      playerMgr.addGold(seat.getPlayerId(),
          convertGold(seat.getTable().getRoom().getRoomDomain(), -bet), LogReason.BLACKJACK_SPLIT);

      seat.setTotalChips(seat.getTotalChips() - bet);

      // 通知补牌结束
      msgMgr.noticePlayerSplit(seat);

      // 下个回合
      doPlayerOp(seat.getTable());
    }

    return ResMessage.DEFAULT;
  }


  /**
   * 补牌 .
   *
   * @author abin
   * @date 2018年3月27日 下午5:22:25
   * @param player 玩家．
   * @return 结果．
   */
  public ResMessage hit(Player player) {
    BlackjackSeat seat = dataMgr.getPlayerSeat(player.getId());
    HuohuaAssert.isTrue(seat.getOrder() == seat.getTable().getActiveSeatOrder() && seat.isHasBet());

    BlackjackCardSeat cardSeat = seat.getCardSeats().get(seat.getTable().getActiveCardOrder());
    HuohuaAssert.isTrue(cardSeat != null && !cardSeat.isOpOver() && !cardSeat.isBill());

    HuohuaAssert
        .isTrue(BlackjackCardsTypeGetter.canHit(cardSeat.getCardTypeAndPoint().getCardsType()));

    // 补牌成功
    if (seat.getTable().stepFuture.cancel(false)) {

      // 补牌
      seat.getTable().dealHitCard(cardSeat, seat.isSplit());

      // 校验．
      boolean isBust = opCheck(cardSeat);

      // 爆牌结算
      if (isBust) {
        playerBustBill(player, seat, cardSeat);

        LOG.info("[21点]{} 补牌成功   玩家{} 座位{} 虚拟牌组座位{} 爆牌结算", seat.getTable().getId(),
            seat.getPlayerId(), seat.getOrder(), cardSeat.getCardSeatOrder());
      }

      // 通知补牌结束
      msgMgr.noticePlayerHit(seat, cardSeat);

      LOG.info("[21点]{} 补牌成功   玩家{} 座位{} 虚拟牌组座位{} ", seat.getTable().getId(), seat.getPlayerId(),
          seat.getOrder(), cardSeat.getCardSeatOrder());

      seat.setChangeBet(0);
      // 下个回合
      doPlayerOp(seat.getTable());
    } else {
      LOG.info("停止超时操作失败");
    }


    return ResMessage.DEFAULT;
  }

  /**
   * 站起 .
   *
   * @author abin
   * @date 2018年3月28日 上午10:27:21
   * @param player 玩家.
   * @return 结果.
   */
  public ResMessage stand(Player player) {
    BlackjackSeat seat = dataMgr.getPlayerSeat(player.getId());
    HuohuaAssert.isTrue(seat.getOrder() == seat.getTable().getActiveSeatOrder() && seat.isHasBet());

    BlackjackCardSeat cardSeat = seat.getCardSeats().get(seat.getTable().getActiveCardOrder());
    HuohuaAssert.isTrue(cardSeat != null && !cardSeat.isOpOver() && !cardSeat.isBill());

    HuohuaAssert
        .isTrue(BlackjackCardsTypeGetter.canStand(cardSeat.getCardTypeAndPoint().getCardsType()));

    // 站起成功
    if (seat.getTable().stepFuture.cancel(false)) {

      cardSeat.setOpOver(true);
      // 通知站起
      msgMgr.noticePlayerStand(seat);

      // 下个回合
      doPlayerOp(seat.getTable());
    }


    return ResMessage.DEFAULT;
  }



  /**
   * 清除玩家.
   * 
   * @param playerId 玩家Id.
   */
  private void doClearGameData(long playerId) {
    try {
      dataMgr.removePlayerSeat(playerId);
      /*
       * 清除玩家房间数据 .
       */
      for (BlackjackRoom room : dataMgr.allRooms()) {
        room.getPlayers().remove(playerId);

        /*
         * 清除房间中玩家所在桌子数据 .
         */
        for (BlackjackTable table : room.getTables().values()) {
          // 是否在桌子中
          boolean inTable = false;
          for (BlackjackSeat s : table.getSeats()) {
            if (s.getPlayerId() > 0 && s.getPlayerId() == playerId) {
              inTable = true;
              break;
            }
          }

          if (inTable) { // 在桌子中清除数据
            for (BlackjackSeat seat : table.getSeats()) {
              if (seat.getPlayerId() > 0) {
                dataMgr.removePlayerSeat(seat.getPlayerId());
                room.getPlayers().remove(seat.getPlayerId());

                /*
                 * 清除座位数据 .
                 */
                seat.clear();
                Player player = playerMgr.getPlayer(seat.getPlayerId());
                if (player != null) {
                  loginMgr.noticeLogoutForGameExit(player);
                }
              }
            }
          }
        }
      }

      Player player = playerMgr.getPlayer(playerId);
      if (player != null) {
        player.curRoom = null;
        player.curTable = null;
        player.curSeat = null;
        loginMgr.noticeLogoutForGameExit(player);
      }
    } catch (Exception e) {
      LOG.error("[21点]清除玩家[" + playerId + "]卡限失败", e);
    }

  }



}
